#include "rvm.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>


#define MAX_SEGMENTS 100
#define MAX_MODIFICATIONS 50

#define LOG_FILE "rvm.log"

typedef struct
{
	int segname_length;
	int offset;
	int size;
} log_datahead_t;

typedef struct
{
	int offset;
	int size;
	void *buffer;
} mod_t;

typedef struct
{
	rvm_t rvm;
	const char *segname;
	const void *segbase;
	int segsize;

	trans_t tid;
	int modified;
	mod_t mods[MAX_MODIFICATIONS];
} seg_t;

int next_rvm_id = 1;
int next_trans_id = 1;

seg_t segments[MAX_SEGMENTS];
int segnum = 0;

int verbose = 1;



void create_log(const char *dir) {
	char logpath[PATH_LENGTH];
	snprintf(logpath, PATH_LENGTH, "%s/%s", dir, LOG_FILE);

	FILE *fp = fopen(logpath, "a");
	fclose(fp);
}

extern rvm_t rvm_init(const char *directory) {
	rvm_t rvm;

	rvm.rvm_id = next_rvm_id++;
	snprintf(rvm.dir, PATH_LENGTH, "%s", directory);

	int result = mkdir(directory, 0700);

	create_log(directory);

	if (verbose) { printf("[Library] RVM initialized with backing store: %s\n", rvm.dir); }

	return rvm;
}



int get_segindex_by_rvm_name(rvm_t rvm, const char *segname) {
	int i;

	for (i = 0; i < segnum; i++) {
		if (!strcmp(segments[i].segname, segname) && segments[i].rvm.rvm_id == rvm.rvm_id) return i;
	}

	return -1;
}

int get_segindex_by_rvm_base(rvm_t rvm, const char *segbase) {
	int i;

	for (i = 0; i < segnum; i++) {
		if (segments[i].segbase == segbase && segments[i].rvm.rvm_id == rvm.rvm_id) return i;
	}

	return -1;
}

int get_segindex_by_tid_base(trans_t tid, const char *segbase) {
	int i;

	for (i = 0; i < segnum; i++) {
		if (segments[i].segbase == segbase && segments[i].tid == tid) return i;
	}

	return -1;
}

void create_segment(const char *segpath, int size) {
	FILE *fp = fopen(segpath, "w");
	ftruncate(fileno(fp), size);
	fclose(fp);
}

void extend_segment(const char *segpath, int size) {
	FILE *fp = fopen(segpath, "r+");
	ftruncate(fileno(fp), size);
	fclose(fp);
}

void create_or_extend_segment(const char *segname, const char *segpath, int target_size) {
	struct stat st;
	int result = stat(segpath, &st);

	if (result == -1) {

		if (verbose) { printf("[Library] Segment %s does not exist\n", segname); }
    	create_segment(segpath, target_size);
    	if (verbose) { printf("[Library] Segment %s created with size %d\n", segname, target_size); }

	} else if (result == 0) {

		if (verbose) { printf("[Library] Segment %s exists and has size %d\n", segname, st.st_size); }
		if (st.st_size < target_size) {
			extend_segment(segpath, target_size);
			if (verbose) { printf("[Library] Segment %s extended from %d to %d\n", segname, st.st_size, target_size); }
		}

	}
}

void *map_segment(const char *segpath, int segsize) {
	FILE *segfile;
	void *segbase;
	
	segfile = fopen(segpath, "r");

	segbase = calloc(segsize, 1);
	fread(segbase, 1, segsize, segfile);
	fclose(segfile);

	return segbase;
}

extern void *rvm_map(rvm_t rvm, const char *segname, int size_to_create) {
	char segpath[PATH_LENGTH];
	void *segbase;

	rvm_truncate_log(rvm);

	if (get_segindex_by_rvm_name(rvm, segname) > -1) {
		fprintf(stderr, "[ERROR] Segment %s cannot be mapped twice!\n", segname);
		exit(1);
	}

	snprintf(segpath, PATH_LENGTH, "%s/%s", rvm.dir, segname);
	if (verbose) { printf("[Library] Segment path: %s\n", segpath); }

	create_or_extend_segment(segname, segpath, size_to_create);

	segbase = map_segment(segpath, size_to_create);

	segments[segnum].rvm = rvm;
	segments[segnum].segname = segname;
	segments[segnum].segbase = segbase;
	segments[segnum].segsize = size_to_create;
	segnum++;

	if (verbose) { printf("[Library] Segment %s is mapped to memory\n", segname); }

	return segbase;
}

void remove_segment(int segindex) {
	int i;
	for (i = segindex; i < segnum-1; i++) {
		segments[i] = segments[i+1];
	}
	segnum--;
}

extern void rvm_unmap(rvm_t rvm, void *segbase) {
	int segindex = get_segindex_by_rvm_base(rvm, segbase);

	if (segindex == -1) {
		fprintf(stderr, "[ERROR] Segment base %d has not been mapped!\n", segbase);
		exit(1);
	}

	remove_segment(segindex);
	free(segbase);
}

extern void rvm_destroy(rvm_t rvm, const char *segname) {
	char segpath[PATH_LENGTH];
	int result;

	if (get_segindex_by_rvm_name(rvm, segname) > -1) {
		fprintf(stderr, "[ERROR] Segment %s cannot be destroyed because it is currently mapped!\n", segname);
		exit(1);
	}

	snprintf(segpath, PATH_LENGTH, "%s/%s", rvm.dir, segname);
	result = remove(segpath);

	if (verbose) { printf("[Library] Segment %s is destroyed from backing store\n", segname); }
}

extern trans_t rvm_begin_trans(rvm_t rvm, int numsegs, void **segbases) {
	int i, segindex;
	trans_t tid;

	for (i = 0; i < numsegs; i++) {
		segindex = get_segindex_by_rvm_base(rvm, segbases[i]);
		if (segindex == -1) {
			fprintf(stderr, "[ERROR] Some of the segments are not mapped\n");
			return (trans_t) -1;
		}
		if (segments[segindex].tid != ((trans_t) 0)) {
			fprintf(stderr, "[ERROR] Segment %s is already being modified by Transaction %d\n", 
									segments[segindex].segname, segments[segindex].tid);
			return (trans_t) -1;
		}
	}

	tid = ((trans_t) next_trans_id++);
	for (i = 0; i < numsegs; i++) {
		segindex = get_segindex_by_rvm_base(rvm, segbases[i]);
		segments[segindex].tid = tid;
	}

	if (verbose) { printf("[Library] Transaction %d is started for %d segments\n", tid, numsegs); }

	return tid;
}

extern void rvm_about_to_modify(trans_t tid, void *segbase, int offset, int size) {
	int segindex, modindex;
	void *buffer;

	segindex = get_segindex_by_tid_base(tid, segbase);
	if (segindex == -1) {
		fprintf(stderr, "[ERROR] The specified segment is not among the segments of Transaction %d!\n", tid);
		exit(1);
	}

	modindex = segments[segindex].modified;

	buffer = calloc(size, 1);
	memcpy(buffer, segbase+offset, size);

	segments[segindex].mods[modindex].offset = offset;
	segments[segindex].mods[modindex].size = size;
	segments[segindex].mods[modindex].buffer = buffer;
	segments[segindex].modified++;

	if (verbose) { printf("[Library] Region [%d, %d] of segment %s will be modified in Transaction %d\n",
					 offset, offset+size, segments[segindex].segname, tid); }
}

void write_log(FILE *logfile, const char *segname, const void *segbase, int offset, int size) {
	log_datahead_t datahead;
	
	datahead.segname_length = strlen(segname);
	datahead.offset = offset;
	datahead.size = size;
	
	fwrite(&datahead, sizeof(log_datahead_t), 1, logfile);
	fwrite(segname, strlen(segname), 1, logfile);
	fwrite(segbase + offset, size, 1, logfile);

	if (verbose) { printf("[Library] Region [%d, %d] of segment %s is written into log\n", offset, offset+size, segname); }
}

extern void rvm_commit_trans(trans_t tid) {
	int i, j;
	FILE *logfile;
	char logpath[PATH_LENGTH];

	// open log file
	for (i = 0; i < segnum; i++) {
		if (segments[i].tid == tid) {
			snprintf(logpath, PATH_LENGTH, "%s/%s", segments[i].rvm.dir, LOG_FILE);
			break;
		}
	}

	logfile = fopen(logpath, "a");

	for (i = 0; i < segnum; i++) {
		if (segments[i].tid == tid) {

			for (j = 0; j < segments[i].modified; j++) {

				free(segments[i].mods[j].buffer);

				write_log(logfile, segments[i].segname, segments[i].segbase, segments[i].mods[j].offset, segments[i].mods[j].size);
				
			}

			segments[i].modified = 0;
			segments[i].tid = (trans_t) 0;

		}
	}

	fclose(logfile);

	if (verbose) { printf("[Library] Transaction %d is commited\n", tid); }
}

extern void rvm_abort_trans(trans_t tid) {
	int i, j, offset, size;
	void *segbase, *buffer;

	for (i = 0; i < segnum; i++) {
		if (segments[i].tid == tid) {

			segbase = (void *)segments[i].segbase;

			for (j = 0; j < segments[i].modified; j++) {
				offset = segments[i].mods[j].offset;
				size = segments[i].mods[j].size;
				buffer = segments[i].mods[j].buffer;

				memcpy(segbase + offset, buffer, size);
				free(buffer);
			}

			segments[i].modified = 0;
			segments[i].tid = (trans_t) 0;
		}
	}

	if (verbose) { printf("[Library] Transaction %d is aborted\n", tid); }
}

void update_segment(rvm_t rvm, char *segname, void *data, int offset, int size) {
	char segpath[PATH_LENGTH];
	FILE *segfile;

	snprintf(segpath, PATH_LENGTH, "%s/%s", rvm.dir, segname);
	segfile = fopen(segpath, "r+");

	fseek(segfile, offset, SEEK_SET);
	fwrite(data, size, 1, segfile);

	fclose(segfile);

	if (verbose) { printf("[Library] Region [%d, %d] of segment %s is written from log to backing store\n", 
								offset, offset+size, segname); }
}

extern void rvm_truncate_log(rvm_t rvm) {
	char logpath[PATH_LENGTH];
	FILE *logfile;
	log_datahead_t datahead;
	void *data;
	char *segname;

	snprintf(logpath, PATH_LENGTH, "%s/%s", rvm.dir, LOG_FILE);
	logfile = fopen(logpath, "r+");

	while ( fread(&datahead, sizeof(log_datahead_t), 1, logfile) == 1 ) {

		data = calloc(datahead.size, 1);
		segname = calloc(datahead.segname_length, 1);

		fread(segname, datahead.segname_length, 1, logfile);
		fread(data, datahead.size, 1, logfile);

		if (verbose) { printf("[Library] Log of segment %s is going to be written back to offset %d with size %d\n", 
									segname, datahead.offset, datahead.size); }

		update_segment(rvm, segname, data, datahead.offset, datahead.size);

		free(data);
		free(segname);

	}

	ftruncate(fileno(logfile), 0);

	fclose(logfile);
}

extern void rvm_verbose(int enable_flag) {
	verbose = enable_flag;
}